package org.yscz.task;

import cn.hutool.core.util.ObjectUtil;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.google.gson.JsonObject;
import com.yscz.bs.lib.InquestControlOuterClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttSubscribe;
import org.junit.rules.Timeout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.yscz.common.ViewConfig;
import org.yscz.config.InquestConfig;
import org.yscz.entity.Answer;
import org.yscz.mapper.AnswerMapper;
import org.yscz.mapper.ProjectMapper;
import org.yscz.mqtt.MqttCustomClient;
import org.yscz.utils.NumberRandomSplitUtil;
import org.yscz.webSocket.WebSocket;
import org.yscz.webSocket.service.impl.WebSocketServiceImpl;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class ApplicationRunnerImpl implements ApplicationRunner {

    @Value("${grpc.server.port}")
    private Integer grpcPort;

    @Value("${grpc.server.ip}")
    private String grpcIp;

    @Value("${yscz.alg.frameRate}")
    private int frameRate;

    //压力值范围
    @Value("${yscz.alg.pressure}")
    private Integer pressureYml;

    //最大连续帧数
    @Value("${yscz.alg.maxFrames}")
    private Integer maxFramesYml;

    //最小连续帧数
    @Value("${yscz.alg.minFrames}")
    private Integer minFramesYml;

    //高兴
    @Value("${yscz.alg.joy}")
    private Float joyYml;

    //平和
    @Value("${yscz.alg.natural}")
    private Float naturalYml;

    //厌恶
    @Value("${yscz.alg.disgust}")
    private Float disgustYml;

    //伤心
    @Value("${yscz.alg.sadness}")
    private Float sadnessYml;

    //惊讶
    @Value("${yscz.alg.surprise}")
    private Float surpriseYml;
    //轻蔑
    @Value("${yscz.alg.contempt}")
    private Float contemptYml;

    //害怕
    @Value("${yscz.alg.fear}")
    private Float fearYml;

    //生气
    @Value("${yscz.alg.anger}")
    private Float angerYml;


    @Autowired
    private MqttCustomClient mqttSubscribe;

    @Autowired
    private ProjectMapper projectMapper;

    @Autowired
    private AnswerMapper answerMapper;

    public static InquestConfig inquestConfig;

    //记录每道题对应的异常表情次数
    //public static Map<String,Object> questionNumMap = new HashMap<>();

    @Autowired
    private AsyncTasks asyncTasks;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private WebSocketServiceImpl webSocketService;

    public static Long timeStart;

    public static Long timeLast;

    //第几分钟
    public static Integer minuteNum = 1;

    public static String roomId;
    private final static Object synchronizedObject = new Object();

    public static String algUuid;


    /**
     * 启动执行
     *
     * @param args
     * @throws Exception
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {

        log.info("grpcIp:[{}]", grpcIp);
        inquestConfig = new InquestConfig(grpcIp, grpcPort);

        //根据帧率计算每隔多长时间取一次算法数据
        int millisecond = 30000 / frameRate;

        // MQTT 订阅消息初始化
        MqttSubscribeInit();

        //定时任务获取算法数据
        getAlgDataTask(millisecond, millisecond);
    }

    /**
     * MQTT 订阅消息初始化
     */
    private void MqttSubscribeInit() {
        //订阅消息
        mqttSubscribe.init();
    }


    /**
     * 获取算法数据定时任务
     *
     * @param delay
     * @param period
     */
    public void getAlgDataTask(int delay, int period) {
        log.info("进入定时任务");
        //InquestConfig inquestConfig = new InquestConfig(grpcIp, grpcPort);

        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                try {
                    //获取算法数据
                    InquestControlOuterClass.GetResultReply resultReply = ApplicationRunnerImpl.inquestConfig.getResult();
                    if (ObjectUtil.isNotEmpty(resultReply) && !CollectionUtils.isEmpty(resultReply.getAlgResultList())) {
                        resultReply.getAlgResultList().forEach(algResult -> {
                            if (ObjectUtil.isNotEmpty(algResult)) {
                                String roomId = algResult.getRoomId();
                                ApplicationRunnerImpl.algUuid = algResult.getRoomId();
                                String userId = algResult.getUserId();
                                String imageId = algResult.getUid();   //获取算法给出的告警帧图
                                // 算法之前的时间
                                Long systemTime = System.currentTimeMillis();

                                //测评报告四张异常图片
                                String timeStart = stringRedisTemplate.opsForValue().get(algResult.getRoomId());
                                if (StringUtils.isEmpty(timeStart)) {
                                    //timeStart = String.valueOf(systemTime / 60000);
                                    stringRedisTemplate.opsForValue().set(algResult.getRoomId(), systemTime.toString());
                                    getAlgData(algResult, imageId, systemTime, systemTime);
                                }else {
                                    getAlgData(algResult, imageId, systemTime, Long.valueOf(timeStart));
                                }

                            }
                        });
                    }
                } catch (Exception e) {
                    log.error("ApplicationRunnerImpl.getAlgDataMethod fail  e: [{}]", e.getMessage());
                }
            }
        }, delay, period);
    }


    /**
     * 根据算法数据计算pressure，求出异常数据
     */
    public void getAlgData(InquestControlOuterClass.AlgResult algResult, String imageId, Long timestamp, Long timeStart) {
        //获取算法数据
        InquestControlOuterClass.Emotion emotion = algResult.getEmotion();
        //高兴
        Integer joy = (int) (emotion.getJoy() * 100);
        //厌恶
        Integer sadness = (int) (emotion.getSadness() * 100);
        //伤心
        Integer disgust = (int) (emotion.getDisgust() * 100);
        //惊讶
        Integer surprise = (int) (emotion.getSurprise() * 100);
        //轻蔑
        Integer contempt = (int) (emotion.getContempt() * 100);
        //害怕
        Integer fear = (int) (emotion.getFear() * 100);
        //生气
        Integer anger = (int) (emotion.getAnger() * 100);
        //平和
        Integer neutral = 100 - joy - disgust - sadness - surprise - contempt - fear - anger;

        //计算压力值
        Integer pressure = (int) (joyYml * joy + naturalYml * neutral + naturalYml * neutral + disgustYml * disgust + sadnessYml * sadness + surpriseYml * surprise + contemptYml * contempt + fearYml * fear + angerYml * anger);

        // 实时计算异常  - yumr
        calcAbnormalEmtion(algResult.getRoomId(), pressure, imageId, timestamp, timeStart);

//        //ViewConfig.framesList.add(pressure);
//        //log.info("ViewConfig.framesList:[{}]", ViewConfig.framesList);
//
//        //连续五个大于pressureYml的标志位，如果一次异常中有大于5个的了，则异常数不在加1
//        Boolean flag = true;
//
//        //记录连续大于pressureYml的数量
//        Integer maxFramesNum = 0;
//
//        //记录连续小于pressureYml的数量
//        Integer minFramesNum = 0;

        //判断是否为异常表情
//        if (!CollectionUtils.isEmpty(ViewConfig.framesList)) {
//            //
//            for (Integer pres : ViewConfig.framesList) {
//                log.info("ViewConfig.framesList.size():[{}]", ViewConfig.framesList.size());
//                if (pres >= pressureYml) {
//
//                    ExceptionImages.rangeExceptionImages(new ExceptionImages(imageId, pressure));  // 缓存数据
//                    //如果有小于连续5帧大于maxFramesYml，下一个值小于minFramesYml，则重新计算
//                    minFramesNum = 0;
//
//                    //大于pressureYml加1
//                    maxFramesNum++;
//
//                    if (maxFramesNum >= maxFramesYml && flag) {
//                        //如果一次异常中已经有连续5帧大于pressureYml，说明是一次异常了，后面即使有连续5帧的异常数也不加1，本次异常结束才继续加1
//                        flag = false;
//
//                        //记录每一题的异常答题数
//                        ViewConfig.everyExceptionNum++;
//
//                        //异常数加1
//                        ViewConfig.exceptionFaceNum++;
//
//                        //这里缓存一下异常的时间
//                        timeLast = System.currentTimeMillis();
//
//                        //如果跟传入的参数一致,说明是同一分钟的内容
//                        if (systemTimeStart.equals(timeLast)) {
//                            //每一分钟的异常答题表情次数
//                            ViewConfig.everyMinuteExceptionNum++;
//
//                        } else {
//                            stringRedisTemplate.opsForValue().set(algResult.getRoomId(), String.valueOf(timeLast / 60000));
//
//                            //从第一分钟开始
//                            JSONObject jsonObject = new JSONObject();
//                            jsonObject.put("minuteNum", minuteNum);
//                            jsonObject.put("everyMinuteExceptionNum", ViewConfig.everyMinuteExceptionNum);
//
//                            String json = JSONObject.toJSONString(jsonObject);
//
//                            //当是下一分钟的时候推前一分钟的异常表情次数
//                            try {
//                                WebSocket.sendInfo(json);
//                            } catch (IOException e) {
//                                e.printStackTrace();
//                            }
//
//                            //初始为1 每次进入else加1
//                            minuteNum++;
//
//                            //置为空重新计算
//                            ViewConfig.everyMinuteExceptionNum = 0;
//                        }
//                    }
//                } else {
//                    //如果有少于连续三帧小于minFramesYml，下一个值大于maxFramesYml，则重新计算
//                    maxFramesNum = 0;
//
//                    //小于pressureYml加1
//                    minFramesNum++;
//                    if (minFramesNum > minFramesYml) {
//
//                        //TODO 清空ViewConfig.framesList之前取出本题的最大值pressure
//                        //Integer maxPressure = Collections.max(ViewConfig.framesList);
//                        //ViewConfig.maxPressure = maxPressure;
//
//                        //清空ViewConfig.framesList
//                        ViewConfig.framesList.clear();
//                        //log.info("ViewConfig.framesList.size():[{}]", ViewConfig.framesList.size());
//
//                        //log.info("本次异常结束,ViewConfig.exceptionFaceNum:[{}]", ViewConfig.exceptionFaceNum);
//
//                        //本次异常结束，继续下次异常的计算
//                        flag = true;
//
//                        break;
//                    }
//                }
//            }
//        }


        //TODO 记录每一题的异常表情次数
        //TODO 记录这个题是不是异常题目 规则：这个题目的异常表情次数 > 0则为异常题目 记录到数据库
        //Line line = new Line();
        //line.setQuestionLine(new QuestionLine());
    }

    /**
     * 计算异常表情
     *
     * @param recordUuid     唯一记录ID
     * @param pressure       异常值
     * @param imageId        图片标识
     * @param timeStamp      时间戳 毫秒级
     * @param cacheTimeStamp 缓存时间 分钟级别
     */
    private void calcAbnormalEmtion(String recordUuid, Integer pressure, String imageId, Long timeStamp, Long cacheTimeStamp) {
        //
        //Long timeStampMinute = cacheTimeStamp / 60000;
        if (timeStamp - cacheTimeStamp >= 60000L) {  // 不是同一分钟
            // 分钟维度的下标值， 从1开始
            ViewConfig.minuteNum.getAndIncrement();
            log.info("minuteNum [{}]", ViewConfig.minuteNum.get());
            //每分钟的异常number
            ViewConfig.everyMinuteExceptionNum.set(0);
            // websocket 发送时间维度的异常数据
            webSocketService.sendTimeLineException(recordUuid, false);
            stringRedisTemplate.opsForValue().set(recordUuid, Long.toString(timeStamp));
        }

        /***
         *  下面是计算异常 number
         *  1、异常表情总次数    ViewConfig.exceptionEmotionTotalNum
         *  2、异常表情答题数    ViewConfig.exceptionQuestionTotalNum
         *  3、题目维度的异常数  ViewConfig.everyQuestionExceptionNum
         *  4、时间维度的异常数  ViewConfig.everyMinuteExceptionNum
         */
        //大于阈值
        if (pressure >= pressureYml) {
            // 异常表情集合
            ViewConfig.abnormalEmotionList
                    .add(AbnormalEmotion.builder()
                            .imageName(imageId)
                            .timeStamp(timeStamp)
                            .imageScore(pressure)
                            .build()
                    );
            ViewConfig.lessThanAbnormalNum = 0;
        } else {
            // 小于阈值
            if (++ViewConfig.lessThanAbnormalNum >= minFramesYml) {
                if (ViewConfig.abnormalEmotionList.size() < maxFramesYml) {
                    // 连续小于阈值的帧数 >= minFramesYml， 判断连续大于阈值的帧数 < maxFramesYml
                    // 无效异常，清空缓存资源
                    ViewConfig.lessThanAbnormalNum = 0;
                    ViewConfig.abnormalEmotionList.clear();
                } else {
                    // 连续小于阈值的帧数 >= minFramesYml， 连续大于阈值的帧数 >= maxFramesYml
                    // 有效异常
                    AbnormalEmotionImage.rangeExceptionImages(ViewConfig.abnormalEmotionList);  // 缓存异常图片，比较后保存最大的四张图片数据
                    asyncTasks.saveCacheImageAsyc(new ArrayList<>(AbnormalEmotionImage.abnormalEmotionList));  // 通知流媒体缓存图片

                    try {
                        synchronizedCalcExceptionNumber(recordUuid);
                    } catch (Exception e) {
                        log.error("calc exception :[{}]", e.getMessage());
                    }

                    ViewConfig.lessThanAbnormalNum = 0;
                    ViewConfig.abnormalEmotionList.clear();
                }
            }
        }
    }

    /**
     * 计算异常表情数据
     *
     * @param recordUuid
     */
    private void synchronizedCalcExceptionNumber(String recordUuid) {
        synchronized (synchronizedObject) {
            ViewConfig.exceptionEmotionTotalNum.getAndIncrement();     // 异常表情次数 +1
            if (ViewConfig.everyQuestionExceptionNum.get() == 0) {
                ViewConfig.exceptionQuestionTotalNum.getAndIncrement();
            }
            ViewConfig.everyQuestionExceptionNum.getAndIncrement();
            ViewConfig.everyMinuteExceptionNum.getAndIncrement();
            log.info("exceptionEmotionTotalNum --------------- &&& :[{},{}, {}, {}]",
                    ViewConfig.exceptionEmotionTotalNum.get(),
                    ViewConfig.exceptionQuestionTotalNum.get(),
                    ViewConfig.everyQuestionExceptionNum.get(),
                    ViewConfig.everyMinuteExceptionNum.get());
        }

        if (WebSocket.getMainSession() != null) {
            //推送异常表情次数和异常表情答题数
            Map<String, Object> map = new HashMap<>();
            map.put("exceptionFaceNum", ViewConfig.exceptionEmotionTotalNum.get());
            map.put("exceptionQuestionNum", ViewConfig.exceptionQuestionTotalNum.get());

            JSONObject realTime_push = new JSONObject();
            realTime_push.put("type", "realTime-push");
            realTime_push.put("data", map);
            WebSocket.SendMessage(WebSocket.getMainSession(), JSONObject.toJSONString(realTime_push, SerializerFeature.WriteMapNullValue));

            // 实时推送题目维度 数据
            log.info("application sendQuestionLineException ----------------&& : [{}, {}]", recordUuid, ViewConfig.currentQuestIndex.get());
            Answer answer = null;
            webSocketService.sendQuestionLineException(recordUuid, ViewConfig.currentQuestIndex.get(), true, answer);

            // 实时推送时间维度 数据
            webSocketService.sendTimeLineException(recordUuid, true);
        }
    }


    // 异常图片信息缓存类
    public static class AbnormalEmotionImage {

        public static List<AbnormalEmotion> abnormalEmotionList = new CopyOnWriteArrayList<>();

        public static List<AbnormalEmotion> rangeExceptionImages(List<AbnormalEmotion> abnormalEmotions) {
            abnormalEmotionList.addAll(abnormalEmotions);
            Collections.sort(abnormalEmotionList);
            if (!CollectionUtils.isEmpty(abnormalEmotionList) && abnormalEmotionList.size() > 4) {
                abnormalEmotionList = abnormalEmotionList.subList(0, 4);
            }
            return abnormalEmotionList;
        }

        // 数据库资源和缓存资源重新排序截取
        public static List<AbnormalEmotion> rangeExceptionImages(String imageString) {
            log.info("imageString: [{}]", imageString);
            List<AbnormalEmotion> tableDate = new CopyOnWriteArrayList<>();
            if (StringUtils.isNotEmpty(imageString)) {
                tableDate = JSONArray.parseArray(imageString, AbnormalEmotion.class);
            }
            return rangeExceptionImages(tableDate);
        }
    }

//    public void questionExceptionCount() {
//        //如果是异常答题，异常答题数加1
//        if (ViewConfig.exceptionTimeFlag) {
//            ViewConfig.exceptionTimeFlag = false;
//
//            //根据recordId和questionNum查询出该条答题信息，并更新该条答题记录为异常答题
//            QueryWrapper<Answer> queryWrapper = new QueryWrapper<>();
//            queryWrapper.eq("answer_question_num", ViewConfig.questionNum);
//            queryWrapper.eq("record_id_fk", ViewConfig.recordId);
//            List<Answer> answers = answerMapper.selectList(queryWrapper);
//            Answer answer = answers.get(0);
//            if (answer != null) {
//                //之前不是异常题，修改为异常题目
//                if (1 == answer.getAnswerException()) {
//                    ViewConfig.exceptionQuestionNum++;
//                }
//                answer.setAnswerException(0);
//                //更新为异常题目
//                answerMapper.updateById(answer);
//            }
//        }
//    }
}
